package crmdna.participant;

import static crmdna.common.AssertUtils.ensureEqual;
import static crmdna.common.OfyService.ofy;
import static crmdna.member.MemberSaver.memberSaver;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import com.googlecode.objectify.Key;

import crmdna.client.Client;
import crmdna.common.APIException;
import crmdna.common.APIResponse.Status;
import crmdna.common.contact.Contact;
import crmdna.common.contact.ContactProp;
import crmdna.common.contact.ValidationResultProp;
import crmdna.member.Member;
import crmdna.member.MemberEntity;
import crmdna.member.MemberEntity.MemberFactory;
import crmdna.member.MemberLoader;
import crmdna.participant.ParticipantEntity.ParticipantFactory;
import crmdna.program.Program;
import crmdna.program.ProgramProp;
import crmdna.user.User;
import crmdna.user.User.Action;
import crmdna.user.User.ResourceType;

public class Participant {

	public static List<ParticipantProp> getAll(String client, long programId,
			String login) {

		User.ensureValidUser(client, login);

		Program.safeGet(client, programId);

		List<Key<ParticipantEntity>> keys = ofy(client).load()
				.type(ParticipantEntity.class).filter("programId", programId)
				.keys().list();

		Map<Key<ParticipantEntity>, ParticipantEntity> map = ofy(client).load()
				.keys(keys);

		List<ParticipantProp> participantProps = new ArrayList<>();

		for (Key<ParticipantEntity> key : map.keySet()) {
			participantProps.add(map.get(key).toProp());
		}

		Collections.sort(participantProps);

		return participantProps;
	}

	public static int deleteAll(String client, long programId, String login) {

		Client.ensureValid(client);

		ProgramProp programProp = Program.safeGet(client, programId).toProp();
		User.ensureAccess(client, login, ResourceType.GROUP,
				programProp.groupProp.groupId, Action.BULKWRITE);

		List<Key<ParticipantEntity>> participantKeys = ofy(client).load()
				.type(ParticipantEntity.class).filter("programId", programId)
				.keys().list();

		if (0 == participantKeys.size()) {
			throw new APIException(Status.ERROR_RESOURCE_NOT_FOUND,
					"No participants data found for programId [" + programId
							+ "]");
		}

		Map<Key<ParticipantEntity>, ParticipantEntity> map = ofy(client).load()
				.keys(participantKeys);

		// delete the program for all member records
		List<Long> memberIds = new ArrayList<>();
		for (Key<ParticipantEntity> key : map.keySet()) {
			ParticipantEntity participantEntity = map.get(key);

			if (participantEntity != null)
				memberIds.add(participantEntity.memberId);
		}

		List<MemberEntity> memberEntities = MemberLoader.get(client, memberIds,
				login);

		memberSaver(client, memberEntities).deleteProgramFromAll(programId)
				.populateDependantsAndSave();

		ofy(client).delete().keys(participantKeys);

		return participantKeys.size();
	}

	// public static UploadReportProp uploadAll_old(String client,
	// List<ContactProp> contactProps, long programId,
	// boolean ignoreWarnings, String login) {
	//
	// // This function will save the entered details as is into participant
	// // entities.
	// // If the member is new it will create member entities.
	//
	// // if participant details are already saved, just
	// // throw an error message and exit
	//
	// Client.ensureValid(client);
	// int count = ofy(client).load().type(ParticipantEntity.class)
	// .filter("programId", programId).keys().list().size();
	//
	// if (0 != count) {
	// throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS, "["
	// + count + "] participants already found for program id ["
	// + programId
	// + "]. These participants should be deleted first before "
	// + " another upload. ");
	// }
	//
	// // logic is as follows -
	//
	// // for existing members
	// // 1. add program to memberentity
	// // 2. populate member id to participantentity
	// // for new members
	// // 1. save as members first and get the member id
	// // 2. populate member id to participant entity
	// // save all participant entities
	//
	// ProgramProp programProp = Program.safeGet(client, programId).toProp();
	// User.ensureAccess(client, login, ResourceType.PROGRAM,
	// programProp.groupProp.groupId, Action.BULKWRITE);
	//
	// ValidationResultProp validationResultProp = Contact
	// .validate(contactProps);
	//
	// if (validationResultProp.hasErrors()) {
	// throw new APIException()
	// .status(Status.ERROR_RESOURCE_INCORRECT)
	// .object(validationResultProp)
	// .message(
	// "Found [" + validationResultProp.numErrors
	// + "] errors and ["
	// + validationResultProp.numWarnings
	// + "]. Please correct them before uploading");
	// }
	//
	// if (validationResultProp.hasWarnings() && !ignoreWarnings) {
	// throw new APIException()
	// .status(Status.ERROR_RESOURCE_INCORRECT)
	// .object(validationResultProp)
	// .message(
	// "Found ["
	// + validationResultProp.numWarnings
	// + "] warnings. To ignore warnings set [ignoreWarnings] flag to true");
	// }
	//
	// List<Long> existingMemberIds = Member.getMatchingMemberIds(client,
	// contactProps);
	//
	// List<ParticipantEntity> participantEntities = getParticipantEntities(
	// client, contactProps, programId);
	//
	// List<ContactProp> toAdd = new ArrayList<>();
	//
	// int numExisting = 0;
	// for (int index = 0; index < contactProps.size(); index++) {
	// ContactProp c = contactProps.get(index);
	// ParticipantEntity pe = participantEntities.get(index);
	// Long memberId = existingMemberIds.get(index);
	//
	// if (memberId == null) {
	// toAdd.add(c);
	// } else {
	// toAdd.add(null);
	// pe.memberId = memberId;
	// numExisting++;
	// }
	// }
	//
	// List<Long> newMemberIds = Member.bulkCreateWithoutValidations(client,
	// toAdd, programId);
	//
	// ensureCorrectArraySizes(contactProps.size(), toAdd.size(),
	// existingMemberIds.size(), newMemberIds.size(),
	// participantEntities.size());
	//
	// for (int index = 0; index < newMemberIds.size(); index++) {
	// Long memberId = newMemberIds.get(index);
	// if (memberId != null) {
	// ParticipantEntity pe = participantEntities.get(index);
	// pe.memberId = memberId;
	// }
	// }
	//
	// // add program to existing members
	// Member.addOrDeleteProgram(client, programId, existingMemberIds, true,
	// login);
	//
	// // save all participants entity
	// ofy(client).save().entities(participantEntities);
	//
	// UploadReportProp uploadReportProp = new UploadReportProp();
	// uploadReportProp.numParticipants = contactProps.size();
	// uploadReportProp.numExistingMembers = numExisting;
	// uploadReportProp.numNewMembers = uploadReportProp.numParticipants
	// - numExisting;
	//
	// return uploadReportProp;
	// }

	public static UploadReportProp uploadAll(String client,
			List<ContactProp> contacts, long programId, boolean ignoreWarnings,
			boolean updateProfile, String login) {

		// This function will save the entered details as is into participant
		// entities.
		// If the member is new it will create member entities.

		// if participant details are already saved, just
		// throw an error message and exit

		Client.ensureValid(client);
		int count = ofy(client).load().type(ParticipantEntity.class)
				.filter("programId", programId).keys().list().size();

		if (0 != count) {
			throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS, "["
					+ count + "] participants already found for program id ["
					+ programId
					+ "]. These participants should be deleted first before "
					+ " another upload. ");
		}

		// logic is as follows -

		// for existing members
		// 1. add program to memberentity
		// 2. populate member id to participantentity
		// for new members
		// 1. save as members first and get the member id
		// 2. populate member id to participant entity
		// save all participant entities

		ProgramProp programProp = Program.safeGet(client, programId).toProp();
		User.ensureAccess(client, login, ResourceType.GROUP,
				programProp.groupProp.groupId, Action.BULKWRITE);

		//ValidationResultProp validationResultProp = Contact.validate(contacts);
		ValidationResultProp validationResultProp = Contact.validate2(contacts);

		if (validationResultProp.hasErrors()) {
			throw new APIException()
					.status(Status.ERROR_RESOURCE_INCORRECT)
					.object(validationResultProp)
					.message(
							"Found [" + validationResultProp.numErrors
									+ "] errors and ["
									+ validationResultProp.numWarnings
									+ "] warnings. Please correct them before uploading");
		}

		if (validationResultProp.hasWarnings() && !ignoreWarnings) {
			throw new APIException()
					.status(Status.ERROR_RESOURCE_INCORRECT)
					.object(validationResultProp)
					.message(
							"Found ["
									+ validationResultProp.numWarnings
									+ "] warnings. To ignore warnings set [ignoreWarnings] flag to true");
		}

		List<MemberEntity> memberEntities = Member
				.getMatchingMembersSameSizeList(client, contacts, login);

		List<ContactProp> toAddContactProps = new ArrayList<>();
		List<ContactProp> toUpdateContactProps = new ArrayList<>();
		List<MemberEntity> toUpdateMemberEntities = new ArrayList<>();

		List<Boolean> match = new ArrayList<>(contacts.size());

		for (int index = 0; index < contacts.size(); index++) {
			ContactProp c = contacts.get(index);
			c.asOfyyyymmdd = programProp.endYYYYMMDD;
			MemberEntity memberEntity = memberEntities.get(index);

			if (memberEntity == null) {
				toAddContactProps.add(c);
				match.add(false);
			} else {
				toUpdateContactProps.add(c);
				toUpdateMemberEntities.add(memberEntity);
				match.add(true);

				// never change the member profile email address
				c.email = null;
			}
		}

		ensureEqual(contacts.size(), match.size());
		ensureEqual(toAddContactProps.size() + toUpdateContactProps.size(),
				contacts.size());

		List<MemberEntity> toAddMemberEntities = MemberFactory.create(client,
				toAddContactProps, programProp.groupProp.groupId);

		// save new members
		memberSaver(client, toAddMemberEntities)
				.setContactsSameSizeList(toAddContactProps)
				.addProgramToAll(programId).populateDependantsAndSave();

		// save existing members
		if (updateProfile) {
			// update contact details in member profile. Never change email.
			// email already set to null in one of the above for loops
			memberSaver(client, toUpdateMemberEntities)
					.setContactsSameSizeList(toUpdateContactProps)
					.addProgramToAll(programId).populateDependantsAndSave();
		} else {
			memberSaver(client, toUpdateMemberEntities).addProgramToAll(
					programId).populateDependantsAndSave();
		}

		// populate member id
		memberEntities = new ArrayList<>(contacts.size());
		int addIndex = 0;
		int updateIndex = 0;
		for (int i = 0; i < contacts.size(); i++) {
			MemberEntity memberEntity;
			if (match.get(i)) {
				// already existing member
				memberEntity = toUpdateMemberEntities.get(updateIndex);
				updateIndex++;
			} else {
				// new member
				memberEntity = toAddMemberEntities.get(addIndex);
				addIndex++;
			}

			memberEntities.add(memberEntity);
		}

		List<ParticipantEntity> participantEntities = ParticipantFactory
				.create(client, contacts, memberEntities, programId);
		ensureEqual(participantEntities.size(), contacts.size());

		// save
		ofy(client).save().entities(participantEntities);

		UploadReportProp uploadReportProp = new UploadReportProp();
		uploadReportProp.numParticipants = contacts.size();
		uploadReportProp.numExistingMembers = toUpdateMemberEntities.size();
		uploadReportProp.numNewMembers = uploadReportProp.numParticipants
				- toUpdateMemberEntities.size();

		return uploadReportProp;
	}
}
